package com.mobimap.nhom9.dao;

import java.util.ArrayList;
import java.util.List;

import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.util.SparseArray;

import com.mobimap.nhom9.dao.table.IDTOConverter;
import com.mobimap.nhom9.dao.table.NodeInfoTable;
import com.mobimap.nhom9.dao.table.StreetSegmentTable;
import com.mobimap.nhom9.dto.LocationDTO;
import com.mobimap.nhom9.dto.NodeInfoDTO;
import com.mobimap.nhom9.dto.StreetSegmentDTO;

public class NodeInfoDAO extends OneKeyTableDAO<NodeInfoDTO> {
	private static final NodeInfoTable nodeInfoTable = new NodeInfoTable();

	public NodeInfoDAO(SQLiteDatabase db) {
		super(db, nodeInfoTable);
	}

	public boolean hasNode(LocationDTO location) {
		SQLiteStatement statement = database
				.compileStatement("SELECT COUNT(*) from nodeinfo WHERE NodeInfo.xLocation = ? AND NodeInfo.yLocation = ? LIMIT 1");
		statement.bindDouble(0, location.getLatitude());
		statement.bindDouble(1, location.getLongitude());
		long result = statement.simpleQueryForLong();
		return result == 1;
	}

	public NodeInfoDTO getDTO(LocationDTO location) {
		String sql = "SELECT ID, xLocation, yLocation FROM NodeInfo "
				+ "WHERE NodeInfo.xLocation = ? AND NodeInfo.yLocation = ? LIMIT 1";
		NodeInfoDTO dto = QueryHelper.rawQueryOject(
				database,
				sql,
				new String[] { String.valueOf(location.getLatitude()),
						String.valueOf(location.getLongitude()) }, table);
		return dto;
	}

	public List<NodeInfoDTO> getRelativeNodes(NodeInfoDTO node,
			RelativeNode relative) {
		String sql1 = null;
		String sql2 = null;
		if (relative == RelativeNode.GoTo) {
			sql1 = "SELECT n.ID, n.xLocation, n.yLocation "
					+ "FROM StreetSegment ss, NodeInfo n "
					+ "WHERE ss.StartPoint = ? AND ss.EndPoint = n.ID";
			sql2 = "SELECT n.ID, n.xLocation, n.yLocation "
					+ "FROM StreetSegment ss, NodeInfo n "
					+ "WHERE ss.StartPoint = n.ID AND ss.EndPoint = ? AND ss.TwoWay = 1";
		} else {
			sql1 = "SELECT n.ID, n.xLocation, n.yLocation "
					+ "FROM StreetSegment ss, NodeInfo n "
					+ "WHERE ss.StartPoint = n.ID AND ss.EndPoint = ?";
			sql2 = "SELECT n.ID, n.xLocation, n.yLocation "
					+ "FROM StreetSegment ss, NodeInfo n "
					+ "WHERE ss.StartPoint = ? AND ss.EndPoint = n.ID AND ss.TwoWay = 1";
		}

		List<NodeInfoDTO> result1 = QueryHelper.rawQuery(database, sql1,
				new String[] { String.valueOf(node.getId()) }, nodeInfoTable);
		List<NodeInfoDTO> result2 = QueryHelper.rawQuery(database, sql2,
				new String[] { String.valueOf(node.getId()) }, nodeInfoTable);
		result1.addAll(result2);
		return result1;
	}

	public List<NodeInfoDTO> getRelativeNodes(LocationDTO location,
			RelativeNode relative, boolean determineNoneNode) {

		if (!determineNoneNode) {
			NodeInfoDTO node = getDTO(location);
			if (node != null)
				return getRelativeNodes(node, relative);
		}

		StreetSegmentDAO streetSegmentDAO = new StreetSegmentDAO(database);
		GetRelativeNodesHandler handler = new GetRelativeNodesHandler(location,
				relative);
		streetSegmentDAO.getDTOs(handler);
		return handler.getRelativeNodes();
	}

	/**
	 * Thực hiện lọc lấy các node liên thông với tới 'location' dựa theo nguyên
	 * tắc đỉnh nằm ở giửa và càng gần với đường thẳng nối giửa 2 đỉnh khác thì
	 * gốc tạo thành giửa 3 đỉnh càng lớn càng gần về 180 độ
	 */
	class GetRelativeNodesHandler implements IQueryHandler<StreetSegmentDTO> {
		public LocationDTO location;
		private List<NodeInfoDTO> relativeNodes;
		private double cosMaxAngle;
		private RelativeNode relative;
		private SparseArray<NodeInfoDTO> cache;

		public GetRelativeNodesHandler(LocationDTO location,
				RelativeNode relative) {
			this.location = location;
			this.relativeNodes = new ArrayList<NodeInfoDTO>();
			this.cosMaxAngle = 1;
			this.relative = relative;
			this.cache = new SparseArray<NodeInfoDTO>();
		}

		private boolean interrupt;

		public boolean isInterrupt() {
			return interrupt;
		}

		public void setInterrupt(boolean interrupt) {
			this.interrupt = interrupt;
		}

		private final StreetSegmentTable tb = new StreetSegmentTable();

		public IDTOConverter<StreetSegmentDTO> GetConverter() {

			return tb;
		}

		private NodeInfoDTO getNodeCache(int key) {
			NodeInfoDTO node = null;
			if (cache.indexOfKey(key) < 0) {
				node = getDTO(key);
				cache.put(key, node);
			} else {
				node = cache.get(key);
			}
			return node;
		}

		public boolean handle(StreetSegmentDTO dto) {
			int start = dto.getStartPoint();
			int end = dto.getEndPoint();
			NodeInfoDTO nodeStart = getNodeCache(start);
			NodeInfoDTO nodeEnd = getNodeCache(end);

			double d = LocationDTO.distance(nodeStart.getLocation(),
					nodeEnd.getLocation());
			if (LocationDTO.distance(location, nodeStart.getLocation()) > d)
				return false;
			if (LocationDTO.distance(location, nodeEnd.getLocation()) > d)
				return false;

			// Không dùng Math.acos để tính nhằm tăng độ chính xác
			double cosAngle = calculateCosAngle(nodeStart.getLocation(),
					location, nodeEnd.getLocation());
			// Cosin nghich bien tu 0 - > 180
			if (cosAngle > cosMaxAngle)
				return false;
			if (cosAngle < cosMaxAngle) {
				relativeNodes.clear();
				cosMaxAngle = cosAngle;
			}
			if (relative == RelativeNode.GoTo) {
				relativeNodes.add(nodeEnd);
				if (dto.isTwoWay())
					relativeNodes.add(nodeStart);
			} else {
				relativeNodes.add(nodeStart);
				if (dto.isTwoWay())
					relativeNodes.add(nodeEnd);
			}
			return false;
		}

		private double calculateCosAngle(LocationDTO a, LocationDTO b,
				LocationDTO c) {
			double ab = LocationDTO.distance(a, b);
			double ac = LocationDTO.distance(a, c);
			double bc = LocationDTO.distance(b, c);
			if (ab == 0 || bc == 0) {
				return -1;
			}
			if (ac == 0) {
				// Đề phòng trường hợp nhập liệu điểm đầu trùng điểm cuối
				return 1;
			}

			return ((ab * ab + bc * bc - ac * ac) / (2 * ab * bc)); // Định lí
																	// cos trong
																	// tam giác
		}

		public List<NodeInfoDTO> getRelativeNodes() {
			return relativeNodes;
		}

	}

	public enum RelativeNode {
		GoTo, GoFrom
	}
}